/**
 * SNApp is magical!
 */

// base level package
var snapp = {
    // Object sequence
    objectSequence: 0,
    
	// track urls
	actionURLs: {},
	//set default action URL
	setURL: function(e1,e2) {
		if (!e2) {
			this.actionURLs["DEFAULT"] = e1;
		} else {
			this.actionURLs[e1] = e2;
		}
	},
	//get default action URL
	getURL: function(e1) {
		if (!e1) {
			return this.actionURLs["DEFAULT"];
		} else {
			return this.actionURLs[e1];
		}
	}
	
}

/**
 * 
 *  SNApp Statics package.
 * 
 * 	Static methods for SNApp static catalog access.
 * 
 */ 
snapp.statics = {
	list_cache: new Object(),
	bean_cache: new Object(),
	// Retrieve an item from the catalog.
	get: function(mnemonic, onCatLoad) {
		if (!this.bean_cache[mnemonic]) {
			snob = this.getSnob();
			// set return function
			snob.cat_onCatLoad = onCatLoad;
			// load the object for the given mnemonic
			snob.load( {
				'mnemonic': mnemonic
			} );
			
		}
		else {
			onCatLoad(this.bean_cache[mnemonic]);
		}
	},
	
	list: function(mnemonic, onCatLoad) {
		if (!this.list_cache[mnemonic]) {
			snob = this.getSnob();
			// set mnemonic to snob list 
			snob.setProperty("mnemonic",mnemonic);
			
			// set return function
			snob.cat_onCatLoad = onCatLoad;
			
			// load the object for the given mnemonic
			snob.list( {
				'mnemonic': mnemonic
			} );
			
		}
		else {
			onCatLoad(this.list_cache[mnemonic]);
		}
	},
	
	evaluateResponse: function(curr_snob) {
		if (curr_snob.isArray) {
			if (!this.list_cache[curr_snob.getProperty('mnemonic')])
				this.list_cache[curr_snob.getProperty('mnemonic')] = curr_snob;
				
			for (itr=0;itr<curr_snob.length;itr++) {
				nxt = curr_snob[itr];
				mnemonic = nxt.getProperty('mnemonic');
				if (!this.bean_cache[nxt.getProperty('mnemonic')])
					this.bean_cache[mnemonic]=nxt;
			}
		} else {
			mnemonic = curr_snob.getProperty('mnemonic');
			if (!this.bean_cache[curr_snob.getProperty('mnemonic')])
				this.bean_cache[mnemonic]=curr_snob;
		}
		
		curr_snob.cat_onCatLoad(curr_snob);
		
	},
	
	getSnob: function() {
		// response function
		rf = snapp.statics.evaluateResponse.bind(this);
		
		// get a new snobbject
		snob = new snapp.Object({
			url: snapp.getURL("snapp_util"),
			
			actions: {
				load: 'statics.Load',
				list: 'statics.List'
			},
			
			events: {
				'onLoad': rf,
				'onList': rf
			}
		});
		
		snob.setProperty("SNAppGlobalId",'SNAPP_STATICS');
		
		return snob;
	}	
}


/**
 * Base Connectivity class
 * 	- Represents a snapp connection
 */
snapp.Object = new Class({
	Implements: [Options,Array],
	
	/*
	 * Customizable options
	 */
	options: {
		// response format
		responseFormat: 'xml',
		
		// actions to call for each operation
		actions: {
			load: 'Load',
			list: 'List',
			count: 'Count',
			save: 'Save',
			saveList: 'SaveList'
		},
		
		// Events for call completion
		// Signature:  callBack(this object, responseXML, responseText)
		events: {
			load: null,
			list: null,
			count: null,
			save: null,
			saveList: null,
				
		},
		
		// Request events.  See Mootools "Request" object events.
		onRequest: null,
		onLoadStart: null,
		onProgress: null,
		onComplete: null,
		onCancel: null,
		//onSuccess is overwritten by snapp DA
		onFailure: null,
		onException: null
	},
	
	/*
	 * "Data Transfer Object" 
	 * 
	 * - This object will contain all properties associated
	 *	with storage and retrieval; no more, no less.  
	 *	Any attributes that need to be parameterized will
	 *	exist within this object.  
	 *
	 *	The "setProperty" and "getProperty" methods refer to 
	 *	the bean properties.
	 *
	 */ 
	bean: new Object(),
	getBean: function() {return this.bean;},
	
	/*
	 * Contains the property keys - Necessary for iteration!
	 */
	keys: new Array(),
	
	/*
	 * Segregated list of child objects
	 */
	objectList: new Array(),
	
	/*
	 * Mootools Request object.  This will remain stateful within the snapp.Object.
	 */
	request: null,
	
	/*
	 * Current event name.  
	 */
	eventName: null,
	
	/**
	 * @param Object to parse
	 * @param Options object
	 * 
	 * ** If only one argument is passed, we assume it is "options"
	 */
	initialize: function() {
		obj2parse = null;
		if (arguments.length > 1) {
			obj2parse = arguments[0];
			options = arguments[1];
		} else {
			options = arguments[0];
		}
		this.setOptions(options);
		if (!this.options.url) this.options.url = snapp.getURL();
		
		// unique sequence id
		snapp.objectSequence++;
		this.SNAppID = snapp.objectSequence;
		
		// create actions..
		this.createActions();
		
		// parse object and build structure if given
		if (obj2parse) this.JSP = new this.JSParser(obj2parse);
		
		//alert('Initialized;');
	},

	//set property to bean (Data Transfer Object)
	setProperty: function(snattr,value) {
		this.bean[snattr] = value;	
		if (!this.keys.contains(snattr)) this.keys.push(snattr);
	},
	
	//Returns the property 
	getProperty: function(snattr) {
		return this.bean[snattr];	
	},
	
	deleteProperty: function(prop) {
		delete this.bean[prop];
		this.keys.splice(this.keys.indexOf(prop),1);
	},
	
	/********************************************************************************
	 *  Object Utilities
	 */
	 
	
	containsKey: function(elnm) {
		return this.keys.indexOf(elnm) > -1;
	},
	
	containsValue: function(obj){
        for(var i=0; i<this.keys.length; i++){
            if(this.keys[i]==obj){
                return true;
            }
        }
        return false;
    },
    	
	// recursively gets an array of ALL children disregarding the layer
	getAllChildren: function() {
		this.child_array = null;
		arr = new Array();	
		this._getNextTier(arr);
		return arr;	
	},

    // Recursively search each tier for an object with the specified property and value
    findObject: function(elnm,elval) {
		return this._searchNextTier(elnm,elval);	
	},
	
	containsObject: function(elnm,elval) {
		return this.getProperty(elnm) == elval || 
			(this.findObject(elnm,elval) != null && typeof this.findObject(elnm,elval) != 'undefined'); 	
	},
	
	findArray: function(elnm,elval) {
		return this.findObject(elnm,elval).SNArray;
	},

	/*
	 * Create an action for each listed in options
	 */
	createActions: function() {
		this.getActionHandler = function(activeEvent) {
			return (function(params) {
				this.eventName = activeEvent;
				//alert(typeOf(params));
				if (typeOf(params) == 'element') {
					if (params.nodeName.toUpperCase() == 'FORM') {
						// populate from form
						this.fromForm(params);

						// if it's multipart, save using hidden iframe
						if (typeOf(params.get('enctype')) != 'null'
							&& params.get('enctype').toLowerCase().contains('multipart')) {
							this.saveForm(params);
							return;
						}
						
						params = this.bean;
					} else {
						alert('Not a form!');
					}
				}
				// check for id property for save
				if (params) {
					// if identifier is not present, set it.
					if (!params[this.getProperty('SNAppIdName')] && this.getProperty(this.getProperty('SNAppIdName')))
						params[this.getProperty('SNAppIdName')] = this.getProperty(this.getProperty('SNAppIdName')); 
				}
				
				this.sendRequest(params);
				
				return false;
		
			});
		};
		
		// for each action listed
		for (oa in this.options.actions) {
			
			act = this.options.actions[oa];
			if (act) {
				// set caller
				this[oa] = this.getActionHandler(oa).bind(this);
			}			
		}
	},
	
	// Asynchronously save a form and populate the object based on the response
	saveForm: function(frm) {
		// set required information to form
		frm.set('method','post');
		frm.set('action',this.options.url+'?action='+this.options.actions[this.eventName]);
	
		// if there is no snapp global identifier, get out of here
		if (typeOf(this.getProperty('SNAppGlobalId')) == 'null')
			return;

		for (prop in this.bean) {
			if (typeOf(frm.elements[prop]) == 'null') {
				//alert('adding property to form: '+prop+" = "+this.bean[prop]);
				frm.grab(
					new Element('input',
					{
						'type':'hidden',
						'name':prop,
						'value':this.bean[prop]
					})
				);
			} else {
				//alert('updating property: '+prop+" = "+this.bean[prop]);
				frm.elements[prop].value = this.bean[prop];
			}
		}
		

		hform = new snapp.form.HiddenForm(frm,{
			onComplete: function(frameHTML, frameXML) {
				this.processResponse(frameHTML, frameXML);
			}.bind(this)
		});
		hform.submit();
			
	},
 
	/********************************************************************************
	 * UI Utilities
	 */
	
	// Populate this object's properties based on form values
	fromForm: function(frm) {
		// set properties of object based on form values
		this.els = frm.elements;
		for (this.itr=0;this.itr<this.els.length;this.itr++) {
			this.fel = this.els[this.itr];
			//alert(this.fel.type + ' = ' + this.fel.name);
			//logger.info('Form element Type: '+this.fel.type);
			switch(this.fel.type) {
				case "text":
				case "textarea":
				case "hidden":
				case "password":
					//logger.info('setting property "'+this.fel.name+'" to "'+ this.fel.value + '"',snapp.objects.Object.fromForm);
					this.setProperty(this.fel.name, this.fel.value);
					break;
				case "select-one":
					this.opts = this.fel.options;
					this.selOpt = this.opts[this.opts.selectedIndex];
					this.setProperty(this.fel.name,this.selOpt.value);
					//logger.info('found selector: '+ this.fel.name + ' = ' + this.selOpt.value);
					break;
				case "select-multiple":
					this.arr = new Array();
					this.opts = this.fel.options;
					for (fe=0;fe<this.opts.length;fe++) {
						this.opt = this.opts[fe];
						if (this.opt.selected) this.arr.push(this.opt.value);
					}
					this.setProperty(this.fel.name+'_values',this.arr.toString());
					break;
				case "checkbox":
					this._tstval = frm.elements[this.fel.name][0] ? frm.elements[this.fel.name][0].value : frm.elements[this.fel.name].value;
					//alert(this._tstval);
					if (this._tstval == this.fel.value) {
						this.boxArr = new Array();
						this.boxes = frm.elements[this.fel.name];
						if (this.boxes.length) {
							for (fe=0;fe<this.boxes.length;fe++) {
								this.box = this.boxes[fe];
								if (this.box.checked) this.boxArr.push(this.box.value);
							}
						} else {
							if (this.boxes.checked) this.boxArr.push(this.boxes.value);
						}
						this.setProperty(this.fel.name+'_values',this.boxArr.toString());							

					}
					break;
				case "radio":
					if (this.fel.checked) {
						this.setProperty(this.fel.name,this.fel.value);
					}
					break;
				default:
					break;
			}
		}
		//logger.openLog();
		frm.snob = this;
	},
	
	// Populate form values based on this object's properties
	toForm: function(frm) {
		// save reference to this dbo
		frm.snob = this;
		// set form values based on this object's properties
		this.els = frm.elements;
		for (this.itr=0;this.itr<this.els.length;this.itr++) {
			this.fel = this.els[this.itr];
			//this.doValidation(this.fel);
			switch(this.fel.type) {
				case "text":
				case "textarea":
				case "hidden":
				case "password":
					if (this.getProperty(this.fel.name) != null) {
						this.fel.value = this.getProperty(this.fel.name);
					}
					break;
				case "select-one":
				case "select-multiple":
					try {
						se = this.getProperty(this.fel.name);
						if (se) {
							this.sa = se;
							if(typeof se == 'string') {
								this.sa = se.split(',');
							} else if (se.isArray) {
								this.sa = new Array();
								for(s=0;s<se.length;s++)
									this.sa.push(se[s].getProperty(se[s].getProperty('SNAppIdName'))); 	
							}
							//logger.info('Found selector value > '+this.sa.toString());
							this.opts = this.fel.options;
							for (this.o=0;this.o<this.opts.length;this.o++) {
								if (this.sa.indexOf(this.opts[this.o].value) > -1)
									this.opts[this.o].selected = true;
							}
						}
					} catch(e) {
						logger.warn("Error building selector "+this.fel.name,'snapp.objects.Object.toForm',e);
					}
					break;
				case "checkbox":
				case "radio":
					try {
						if (!this[this.fel.name+'_selectionarray']) {
							se = this.getProperty(this.fel.name);
							if (se) {
								this[this.fel.name+'_selectionarray'] = se;
								if(typeof se == 'string') {
									this[this.fel.name+'_selectionarray'] = se.split(',');
								} else if (se.isArray) {
									this[this.fel.name+'_selectionarray'] = new Array();
									for(s=0;s<se.length;s++) {
										this[this.fel.name+'_selectionarray'].push(se[s].getProperty(se[s].getProperty('SNAppIdName')));
									} 	
								} else {
									this.fel.checked = false;
								}
							} else {
								this.fel.checked = false;
							}
							
						}
						
						if (this[this.fel.name+'_selectionarray'].indexOf(this.fel.value) > -1)
							this.fel.checked = true;
						else
							this.fel.checked = false;
					
					} catch(e) {
						this.fel.checked = false;
						//logger.warn("Error building checkbox/radio "+this.fel.name,'snapp.objects.Object.toForm',e);
					}
					break;
				default:
					break;
			}
		
		}
		//logger.openLog();
	},

	/**
	 * Populate a display area by matching property names to element names
	 */
	toDisplay: function(dispNode) {
		// go through properties
		for (p=0;p<this.keys.length;p++) {
			prop = this.keys[p];
			val = this.getProperty(prop);
			
			// get elements with property name
			pels = $(dispNode).getElements('*[name='+prop+']');
			for (pel=0;pel<pels.length;pel++) {
				pels[pel].innerHTML = val;
			}
		}
	},
	
	/**
	 * Build a selector from this snapp.objects.Object using the given "select" element and configuration
	 */
	toSelector: function(selOb,options) {
		this.selector = new snapp.form.Selector(this, selOb, options);				
		this.selector.buildOptions(selOb);		
	},
	
	/**
	 * Build a checkbox selection area from this list given the wrapper element and configuration
	 */
	toCheckboxList: function(wrpr,options) {
		this.checkboxlist = new snapp.form.CheckboxList(this, wrpr, options);
		this.checkboxlist.build();
	},



	/**************************************************************************
	 * Send the custom request given action and parameters
	 */
	//send ajax given action & Parameters
	sendRequest: function(params, actParams) {
		// reset parameters
		this.parameters = '';
		
		// if an action is provided, use it
		if (this.options.actions[this.eventName]) {
			//set the requested action
			this.parameters = 'action=' + this.options.actions[this.eventName];
		// otherwise, we have no action.  That doesn't work
		} else {
			logger.error('no ' + this.eventName + ' action set for snapp.Object!');
		}
		
		// if there is no snapp global identifier, get out of here
		if (typeOf(this.getProperty('SNAppGlobalId')) == 'null')
			return;
		
		// append the snapp global identifier
		this.parameters += '&SNAppGlobalId='+this.getProperty('SNAppGlobalId');	
	
		// if additional parameters are provided...
		if (params) {
			this.parameters += '&'+Object.toQueryString(params);
		} else {
			this.parameters += '&'+Object.toQueryString(this.bean);
		}
		
		if (!this.request) {
			this.request = new Request({
				method: 'post',
				link: 'cancel', // This will cancel the running request and start the new one
				onSuccess: this.processResponse.bind(this)
			});
			// copy all necessary events to Request
			this.request.setOptions(this.options);
		}
		this.request.send(this.parameters);
		//logger.info('Sending request: (url+params) > ' + this.options.url + '?' + this.parameters, snapp.objects.Connection);
		return false;
	},
	
	// process a response	
	processResponse: function(responseText, responseXML) {
		if (this.options.responseFormat.toLowerCase() == 'xml') {
			// parse XML
			this.XMLP = new snapp.XMLParser(this);
			this.XMLP.parseResponse(responseXML.firstChild);
			
		} else if (this.options.responseFormat.toLowerCase() == 'json') {
			// js parser
			this.JSP = new snapp.JSParser(this);
			this.JSP.parseObject(eval('"'+responseText+'"'));
		}
		
		// call completion event
		if (typeOf(this.options.events[this.eventName]) != 'null') {
			this.options.events[this.eventName](this, responseXML, responseText);
		}
	},
				
		
	/*************************************************************************
	 * 
	 * Utility functions
	 */
	_getNextTier: function(arr) {
		if (this.isArray) {
			this._getChildArray(arr);
			
		} else {
			this._getChildObjects(arr);
		}
 		return arr;
	},
	
	_getChildArray: function(arr) {
		for (this.array_itr=0;this.array_itr<this.length;this.array_itr++) {
			arr.push(this[this.array_itr]);
			this[this.array_itr]._getNextTier(arr);	
		}
	},
	
	_getChildObjects: function(arr) {
		for (this.object_itr=0;this.object_itr<this.objectList.length;this.object_itr++) {
			arr.push(this[this.objectList[this.object_itr]]);
			this[this.objectList[this.object_itr]]._getNextTier(arr);	
		}		
	},
	 
	_searchNextTier: function(elnm,elval) {
		this.nexttier_object = null;	
		if (this.isArray) {
			this.nexttier_object = this._searchArray(elnm,elval);
			if (!this.nexttier_object) this.nexttier_object = this._searchArrayTier(elnm,elval);
		} else {
			this.nexttier_object = this._searchObjects(elnm,elval);
			if (!this.nexttier_object && this.objectList.length > 0) this.nexttier_object = this._searchObjectTier(elnm,elval);
		}
 		return this.nexttier_object;	
 	},
	
	_searchArray: function(elnm,elval) {
		
		for (this.array_itr=0;this.array_itr<this.length;this.array_itr++) {
			this.activesearch_object = this._searchObject(this[this.array_itr],elnm,elval);
			if (this.activesearch_object) return this.activesearch_object;
		}
		return null;
	},
 	
	_searchObjects: function(elnm,elval) {
		for (this.object_itr=0;this.object_itr<this.objectList.length;this.object_itr++) {
			this.activesearch_object = this._searchObject(this[this.objectList[this.object_itr]],elnm,elval);
			if (this.activesearch_object) return this.activesearch_object;
		}
		return null;
	},
	
	_searchObject: function(actpto,elnm,elval) {
		for (e=0;e<actpto.keys.length;e++) {
 			try {
	 			if (actpto.getProperty(elnm) == elval) {
	 				return actpto;
	 			}
 			} catch(e) {}
	 	}
	 	return null;
 	},
	
	_searchArrayTier: function(elnm,elval) {
		for (this.array_itr=0;this.array_itr<this.length;this.array_itr++) {
			this.activesearch_object = this[this.array_itr].findObject(elnm,elval);
			if (this.activesearch_object) return this.activesearch_object; 
		}
		return null;
	},
	
	_searchObjectTier: function(elnm,elval) {
		for (this.object_itr=0;this.object_itr<this.objectList.length;this.object_itr++) {
			this.activesearch_object = this[this.objectList[this.object_itr]].findObject(elnm,elval);
			if (this.activesearch_object) return this.activesearch_object; 
		}
		return null;
	}

});

/******************************************************************
 * XML parser class
 * 
 */
snapp.XMLParser = new Class({

	initialize: function(snappObj) {
		this.snappObject = snappObj;
	},
	
	// parse the results..
	parseResponse: function(xmlnode) {		
		
		if (xmlnode.getElementsByTagName("results")[0]) {
			// set result data
			this.resultNode = xmlnode.getElementsByTagName("results")[0];
			this.resultCode = parseInt(this.resultNode.attributes.getNamedItem("code").value);
			this.resultMessage = this.resultNode.attributes.getNamedItem("message").value;
			
			// result access..
			this.snappObject.getResultCode = function() {return this.resultCode}.bind(this);
			this.snappObject.getResultMessage = function() {return this.resultMessage}.bind(this);
			
			xmlnode = xmlnode.getElementsByTagName("body")[0].childNodes[0];
		}
		
		try {
			// Build the first object
			bob = this.buildObject(xmlnode,this.snappObject);
			
			// Kick off recursion
			this.recurseXML(bob,xmlnode);
		} catch(e) {
			//alert(e);
			//no response...  which can be ok.
			//logger.warn("Could not parse response.  Result Code = "+bob.resultCode+";  Result Message: "+bob.resultMessage, snapp.objects.Parser, e);
		}
	},

	// build a new object, unless provided
	buildObject: function(nd,snob) {
		// if we are given an object, use it
		if (snob) {
			bob = snob;
		
		// otherwise, test for a global name to instantiate next object
		} else {
			if (nd.attributes.getNamedItem("SNAppGlobalName") != null) {
				try {
					eval('bob = new '+nd.attributes.getNamedItem("SNAppGlobalName").value+'();');
				} catch(e) {
					//logger.warn('Could not find bean > '+nd.attributes.getNamedItem("SNAppGlobalName").value, snapp.objects.Parser);
					bob = new snapp.Object();
				}
			} else {
				bob = new snapp.Object();
			}	
		}
		
		// Test for array, and set accordingly
		if (this.nodeIsArray(nd)) {
			bob.isArray = true;
		}
		
		// Set properties based on attributes
		snatts = nd.attributes;
		for (snatt=0;snatt<snatts.length;snatt++) {
			atrbt = snatts.item(snatt);
			bob.setProperty(atrbt.name.toString(),atrbt.value.toString()); 
	 	}
	 	// set the node name to the object
		bob.NODENAME = nd.nodeName;
		
		return bob;
	},	

	// test to see if the current node is an arrayk
	nodeIsArray: function(nnd) {
		_rv = false;
		// get child nodes
		_chs = nnd.childNodes;
		if (_chs) {
			// gather child nodes into a copy
			_chnodes = new Array();
			for (ch1=0;ch1<_chs.length;ch1++) {
				if (parseInt(_chs.item(ch1).nodeType) == 1) {
					_chnodes.push(_chs.item(ch1));
				}
			}
			// if we have more than one child...
			if (_chnodes.length > 1) {
				// default is to assume they are the same name
				same = true;
				// test to verify all children are named the same
				for(ch2=0;ch2<_chnodes.length;ch2++) {
					nxt = _chnodes[ch2];
					// set the first child name
					if (ch2 == 0) ndnm = nxt.nodeName;
					// if we do not match the name, this is not an array.
					if (ndnm != nxt.nodeName) same = false;
				}
				// if all child nodes are named the same, we have an array
				if (same) {
					_rv = true;
				}
			
			// if we have only one child, default to array
			} else if (_chnodes.length == 1) {
				_rv = true;
			}
			
		}
		//alert(nnd.nodeName + (_rv ? " is an array" : " is NOT an array"));
		return _rv;
	},

	// Base for recursively building the next layer
	recurseXML: function(ob,node) {
		this.pob = ob;
		this.chnodes = this.getNodeChildren(node);
		for(this.ch=0;this.ch<this.chnodes.length;this.ch++) {
			this.nob = this.buildObject(this.chnodes[this.ch]);
			this.nob.XMLP = new snapp.XMLParser(this.nob);
			this.nob.XMLP.parseResponse(this.chnodes[this.ch]);					
			if (this.pob.isArray) {
				this.pob.push(this.nob);
				//alert('adding '+this.nob.NODENAME+' to array '+this.pob.NODENAME);
			} else {
				this.pob.setProperty(this.nob.NODENAME,this.nob);	
				//alert('adding '+this.nob.NODENAME+' as property to '+this.pob.NODENAME);
			}
		} 
	},
	
	// Get the direct child nodes for the given node
	getNodeChildren: function(node,pob) {
		// empty copy
		nl = new Array();		
		// child nodes
		chs = node.childNodes;
		if (chs) {
			for (ch=0;ch<chs.length;ch++) {
				nxt = chs.item(ch);
				// if it is an element, add it to the copy
				if (parseInt(nxt.nodeType) == 1) {
					nl.push(nxt);
				}  
			}
		}
		return nl;
	}
		
});
		
/******************************************************************
 * JS parser class
 * 
 * This class is always bound to snapp.Object
 * and is referenced as this.JSP.  
 * This ensures that only one instance of this class can exist
 * at any given time within the snapp.Object.
 */
snapp.JSParser = new Class({
	
	initialize: function(snappObj) {
		this.snappObject = snappObj;
	},
	
	// parse the results..
	parseObject: function(obj2parse) {		
		
		if (obj2parse.results) {
			// set result data
			this.resultCode = parseInt(this.results.code);
			this.resultMessage = this.results.message;
			obj2parse = this.results.body;
		}
		try {
			// Build the first object
			snob = this.buildObject(obj2parse,this.snappObject);
			// Kick off recursion
			this.recurse(obj2parse,snob);
			
		} catch(e) {
			//no response...  which can be ok.
			//alert(e);
			//logger.warn("Could not parse js object.  Result Code = "+bob.resultCode+";  Result Message: "+bob.resultMessage, snapp.objects.Parser, e);
		}
	},
	
	// build a new object, unless provided
	buildObject: function(jsob,snob) {
		// if we are given an object, use it
		if (snob) {
			bob = snob;
		
		// otherwise, test for a global name to instantiate next object
		} else {
			if (jsob.SNAppGlobalName != null) {
				try {
					eval('bob = new '+jsob.SNAppGlobalName+'();');
				} catch(e) {
					//logger.warn('Could not find bean > '+nd.attributes.getNamedItem("SNAppGlobalName").value, snapp.objects.Parser);
					bob = new snapp.Object();
				}
			} else {
				bob = new snapp.Object();
			}	
		}
		
		// Test for array, and set accordingly
		if (typeOf(jsob) == 'array') {
			// set boolean
			bob.isArray = true;
			
			// copy methods and values
			bob.implement(Array);
			for (m=0;m<jsob.length;m++) {
				bob[m] = jsob[m];
			}
		} 
		return bob;
	},	

	// Base for recursively building layers
	recurse: function(jsob,snob) {
		
		if (typeOf(jsob) == 'array') {
			for (nx=0;nx<jsob.length;nx++) {
				if (typeOf(jsob[nx]) == 'array' || typeOf(jsob[nx]) == 'object') {
					snob[nx] = this.buildObject(jsob[nx]);
					snob[nx].JSP = new snob[nx].JSParser(jsob[nx]);
				}
			}
		} else {
			for (nx in jsob) {
				if(typeOf(jsob[nx]) == 'object') {
					nob = this.JSP.buildObject(jsob[nx]);
					snob[nx].setProperty(nx,nob); 
					nob.JSP = new snapp.JSParser(jsob[nx]);
				} else {
					snob.setProperty(nx,jsob[nx]);
				}
			}
		}
	},
	
	
});


/*************************************************************************************************************
 * snapp.form package
 *************************************************************************************************************/
snapp.form = {
	//create the illusion of an asynch submission of a form
	HiddenForm: new Class({
		Implements: [Options,Events],
		
		options: {
			onComplete: null
		},
	
		_frame: null,
		
		initialize: function(frm, options) {
			this.hForm = frm;
			this.setOptions(options);
			
			if ($('hiddenFrame')) $('hiddenFrame').destroy();
			
		    // create a hidden frame
		    this._frame = document.createElement('iframe');
		    this._frame.setAttribute('id','hiddenFrame');
		    this._frame.setAttribute('name', 'hiddenFrame');
		    this._frame.style.display = 'none';
		    this.hForm.parentNode.appendChild(this._frame);
		
		    // Damn IE
		    window.frames['hiddenFrame'].name = 'hiddenFrame';
		    // set form target to frame
		    this.hForm.setAttribute('target','hiddenFrame');
		    
	    	this._frame.addEvent('load',function() {
		    		this.fireEvent('complete',[this._frame.contentDocument.innerHTML,this._frame.contentDocument.childNodes[0]]);
		    		//alert('event fired');
		    	}.bind(this)
	    	);
	    	
		},
		
		submit: function() {
	    
		    // submit the form
		    this.hForm.submit();
		
			//logger.info('ASyncSubmit Response: '+_frame.innerHTML);
		    return this._frame;
		},
		
	}),
	
	selectOption: function(selElement, selVal) {
		this.foundOption = false;
		for (this.o=0;this.o<selElement.options.length;this.o++) {
			if (selElement.options[this.o].value == selVal) {
				selElement.options[this.o].selected = true;
				this.foundOption = true;
			}
		}
		return this.foundOption;
	},
	
	/***********************************************************************
	 * Selector class - build a selector using the given db.Object
	 */
	Selector: new Class({
		Implements: Options,
		
		options: {
			displayProperty: 'name',
			valueProperty: 'id',
			idProperty: 'id',
			altProperty: null,
			selected: null,
			prompt: null,
			all: null,
			trunc: null,
			disabled: false
		},
		
		// constructor
		initialize: function(snob, selOb, options) {
			this.SNObject = snob;
			this.selectElement = $(selOb);
			this.setOptions(options);
		},
		
		/**
		 * Add an option to the select
		 */
		addOption: function (sel,opt){
			
			// format new option...
			o = new Option();
			o.value = opt.getProperty(this.options.valueProperty);
			if (this.options.idProperty)
				o.id = opt.getProperty(this.options.idProperty);
			
			txt = opt.getProperty(this.options.displayProperty);
			
			// check text truncation
			if (this.options.trunc > 0) {
				if (txt.toString().length > parseInt(this.options.trunc)) {
					txt = txt.toString().substring(0,(parseInt(this.options.trunc) - 3)) + "...";
				} else { txt = txt.toString().substring(0,this.options.trunc);}
			}
						
			o.text = txt;
			
			// test for selected value
			o.selected = this.testSelected(opt);
			
			// check for altrnate text
			if (this.options.altProperty) {
				hv = opt.getProperty(this.options.altProperty);
				o.alt = hv;
				o.title = hv;
			}
			
			// push into options array
			sel.options[sel.options.length] = o;
		},
		
		/** test for selected value */
		testSelected: function (opt) {
			// return whether or naot we should be selected
			if (this.selected != null) {
				return opt.getProperty(this.options.valueProperty) == this.selected;
			} else if(this.SNObject.getProperty('selected')) {
				return opt.getProperty(this.options.valueProperty) == this.SNObject.getProperty('selected');
			} 
		},
		
		/** Add prompt */
		addPrompt: function (sel) {
			o = new Option();
			o.value = '-1';
			o.text = this.options.prompt;
			
			// push into options array
			sel.options[sel.options.length] = o;
		},
		
		/** Add all option */
		addAll: function (sel) {
			o = new Option();
			o.value = '0';
			o.text = this.options.all;
			
			// push into options array
			sel.options[sel.options.length] = o;
		},
		
		/** Get selected values */
		getValue: function() {
			selopts = new Array();
			opts = this.selectElement.options;
			for (op=0;op<opts.length;op++) {
				opt = opts[op];
				if (opt.selected) {
					selopts.push(opt.value);
				}
			}
			return selopts.toString();
		},
		
		/**
		 * Build selector options..
		 */				
		buildOptions: function(sel) {
			this.selectElement = sel;
			// set disabled
			sel.disabled = this.options.disabled;
			sel.onmousedown="";
			sel.onmousemove="";
			sel.onmouseup="";
			
			// make sure the options length is reset
			if (!sel.options) sel.options = new Array();
			sel.options.length = 0;
			
			// check for prompt and/or all
			if (this.options.prompt != null) this.addPrompt(sel);
			if (this.options.all != null) this.addAll(sel);	
			
			//alert(this.PTO.ARRAY.length);
			
			if (this.SNObject && this.SNObject.length > 0) {
				// loop and build options...
				for (i=0;i<this.SNObject.length;i++) {
					this.addOption(sel,this.SNObject[i]);
				}
			}
			return sel;
		},
		
		selectOption: function(optVal) {
			for (opt=0;opt<this.selectElement.options.length;opt++) {
				if (this.selectElement.options[opt].value == optVal)
					this.selectElement.options[opt].selected=true;
			}
		}

	}),
	
	/**
	 * Build a checkbox selection area from a db.Object in the given contentElement, using the given config
	 */
	CheckboxList: new Class({
		Implements: Options,
		
		options: {
			
			displayProperty: 'name',
			valueProperty: 'id',
			idProperty: 'id',
			altProperty: null,
			
			displayClass: null,
			boxClass: null,
			
			boxname: null,
			onchange: null,
			
			columns: 2
		},
		
		// constructor
		initialize: function(snob, contentElement, options) {
			this.SNObject = snob;
			this.contentElement = $(contentElement);
			this.setOptions(options);
		},
		
		/*
		 * build it
		 */
		build: function() {
			this.colcount = 0;
			
			this.table = new Element("table");
			this.table.width="100%";
			
			this.tr = this.table.insertRow(-1);
			//alert(this.SNObject.length);
			for(d=0;d<this.SNObject.length;d++) {
				this.cdbo = this.SNObject[d];
				if (this.colcount < this.options.columns) {
					this.td = this.tr.insertCell(-1);
					this.colcount++;
					// create box
					npt = new Element('input');
					npt.type = 'checkbox';
					npt.name = this.options.boxname ? this.options.boxname : 'noname';
					npt.value = this.options.valueProperty 
						? this.cdbo.getProperty(this.options.valueProperty) 
						: this.cdbo.getProperty(this.options.idProperty);
					if (this.options.boxClass) npt.className = this.options.boxClass;
					if (this.options.altProperty) {
						npt.alt = this.cdbo.getProperty(this.options.altProperty);
						npt.title = this.cdbo.getProperty(this.options.altProperty);
					}
					if (this.options.onchange) npt.onchange = function() {
						this.options.onchange(this.cdbo.getProperty(this.options.valueProperty));
					}
					
					this.td.appendChild(npt); 
				
					// display
					spn = new Element('span');
					if (this.options.displayClass) spn.className = this.options.displayClass;
					spn.innerHTML = this.cdbo.getProperty(this.options.displayProperty);
					
					this.td.appendChild(spn);
					
					// if we hit the max column count, insert row
					if (this.colcount == this.options.columns) {
						this.colcount = 0;
						this.tr = this.table.insertRow(-1); 
					}
				}
			}
			if (this.colcount < this.options.columns) {
				try{
					this.td.colspan = (this.options.columns-this.colcount+1);
				} catch(e){}
			}
			
			this.contentElement.grab(this.table);
			return this.table;	
		}
		
	}), 
		
	
	validate: function(frm) {
		// TODO - Convert to mootools validation;
	}		
	
}
